package sim32;
import java.util.TreeMap;

/**
 * Class containing the map between opcode/funct fields and instruction classes/types
 * @author Piotr
 *
 */
public class InstructionDecoder {
	private TreeMap<InstructionType, InstructionClass> iClassMap;
	private TreeMap<String, InstructionType> iTypeMap;
	private TreeMap<String, InstructionType> functCodeMap;
	
	public InstructionDecoder() {
		iClassMap = new TreeMap<InstructionType, InstructionClass>();
		iTypeMap = new TreeMap<String, InstructionType>();
		functCodeMap = new TreeMap<String, InstructionType>();
		
		// Set all supported opcodes
		iTypeMap.put("000000", InstructionType.XRTYPE);
		iTypeMap.put("001111", InstructionType.LUI);
		iTypeMap.put("001000", InstructionType.ADDI);
		iTypeMap.put("001101", InstructionType.ORI);
		iTypeMap.put("100011", InstructionType.LOAD);
		iTypeMap.put("101011", InstructionType.STORE);
		iTypeMap.put("000001", InstructionType.XBRANCH);
		
		// Set all instruction classes
		iClassMap.put(InstructionType.ADD, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.SUB, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.AND, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.OR, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.NOR, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.MUL, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.NOP, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.SLL, InstructionClass.SHIFT);
		iClassMap.put(InstructionType.SRA, InstructionClass.SHIFT);
		iClassMap.put(InstructionType.SRL, InstructionClass.SHIFT);
		iClassMap.put(InstructionType.SLLV, InstructionClass.SHIFTV);
		iClassMap.put(InstructionType.SRAV, InstructionClass.SHIFTV);
		iClassMap.put(InstructionType.SRLV, InstructionClass.SHIFTV);
		iClassMap.put(InstructionType.ADDI, InstructionClass.IMM);
		iClassMap.put(InstructionType.LUI, InstructionClass.IMM);
		iClassMap.put(InstructionType.ORI, InstructionClass.IMM);
		iClassMap.put(InstructionType.LOAD, InstructionClass.MEM);
		iClassMap.put(InstructionType.STORE, InstructionClass.MEM);
		iClassMap.put(InstructionType.BEZ, InstructionClass.BRANCH);
		iClassMap.put(InstructionType.BLZ, InstructionClass.BRANCH);
		iClassMap.put(InstructionType.BLEZ, InstructionClass.BRANCH);
		iClassMap.put(InstructionType.BGZ, InstructionClass.BRANCH);
		iClassMap.put(InstructionType.BGEZ, InstructionClass.BRANCH);
		iClassMap.put(InstructionType.BNEZ, InstructionClass.BRANCH);
		iClassMap.put(InstructionType.JALR, InstructionClass.BRANCH);
		
		//Set all funct/code fields
		functCodeMap.put("100000", InstructionType.ADD);
		functCodeMap.put("100010", InstructionType.SUB);
		functCodeMap.put("100100", InstructionType.AND);
		functCodeMap.put("100101", InstructionType.OR);
		functCodeMap.put("100111", InstructionType.NOR);
		functCodeMap.put("011000", InstructionType.MUL);
		functCodeMap.put("000000", InstructionType.SLL);
		functCodeMap.put("000010", InstructionType.SRL);
		functCodeMap.put("000011", InstructionType.SRA);
		functCodeMap.put("000100", InstructionType.SLLV);
		functCodeMap.put("000110", InstructionType.SRLV);
		functCodeMap.put("000111", InstructionType.SRAV);
		functCodeMap.put("001001", InstructionType.JALR);
		functCodeMap.put("00000", InstructionType.BEZ);
		functCodeMap.put("00001", InstructionType.BLZ);
		functCodeMap.put("00010", InstructionType.BLEZ);
		functCodeMap.put("00011", InstructionType.BGZ);
		functCodeMap.put("00100", InstructionType.BGEZ);
		functCodeMap.put("00101", InstructionType.BNEZ);
		
	}
	
	/**
	 * Checks if the supplied opcode corresponds to a valid instruction.
	 * @param opcode
	 * @return true for valid inputs
	 */
	public boolean isValid(String opcode) {
		if (iTypeMap.containsKey(opcode)) {
			return true;
		} else
			return false;
	}
	
	/**
	 * Returns the InstructionType corresponding to the opcode.
	 * @param opcode
	 * @return InstructionType
	 */
	public InstructionType decodeType(String opcode) {
			return iTypeMap.get(opcode);
	}
	
	/**
	 * Returns the InstructionClass for the given InstructionType.
	 * @param type
	 * @return corresponding InstructionClass
	 */
	public InstructionClass decodeClass(InstructionType type) {
		return iClassMap.get(type);
	}
	
	/**
	 * Returns the InstructionClass for the given opcode.
	 * @param opcode
	 * @return corresponding InstructionClass
	 */
	public InstructionClass decodeClass(String opcode) {
		InstructionType type = iTypeMap.get(opcode);
		return iClassMap.get(type);
	}
	
	/**
	 * Decodes bitString value to fill in necessary fields.
	 * @return 0 if successful, 1 if invalid instruction
	 */
	public int decodeInstruction(Instruction instr) {
		String opcode = new String(instr.bitString.substring(0, 6));
		//System.out.println(opcode);
		if (isValid(opcode)) {
			//System.out.println("Opcode valid");
			instr.iType = decodeType(opcode);
			if (instr.iType == InstructionType.XRTYPE) {
				String funct = new String(instr.bitString.substring(26, 32));
				instr.iType = functCodeMap.get(funct);
			}
			if (instr.iType == InstructionType.XBRANCH) {
				String code = new String(instr.bitString.substring(11, 16));
				instr.iType = functCodeMap.get(code);
			}
			instr.iClass = decodeClass(instr.iType);
			decodeRegNumbers(instr);
			return 0;
		} else
			//System.out.println("Opcode invalid");
			return 1;
	}
	
	/**
	 * Fills in register args/values using rs, rt, rd, shift, imm fields in instruction
	 * Fills in:
	 * @param instr
	 */
	private void decodeRegNumbers(Instruction instr) {
		int temp;
	
		instr.dest1Reg = -1;
		instr.dest2Reg = -1;
		instr.arg1Reg = -1;
		instr.arg2Reg = -1;
	
		switch (instr.iClass) {
			case RTYPE: 			//r-type instructions: add, sub, and, or, nor, mul
				temp = Integer.parseInt(instr.bitString.substring(16,21), 2);
				instr.dest1Reg = temp;
				if (instr.iType == InstructionType.MUL) {
					temp = Integer.parseInt(instr.bitString.substring(21,26), 2);
					instr.dest2Reg = temp;  //Most significant half
				}
				temp = Integer.parseInt(instr.bitString.substring(6,11), 2);
				instr.arg1Reg = temp;	
				temp = Integer.parseInt(instr.bitString.substring(11,16), 2);
				instr.arg2Reg = temp;
				break;
				
			case IMM: 		//Instructions with immediate
				temp = Integer.parseInt(instr.bitString.substring(11,16), 2);
				instr.dest1Reg = temp;
				if (instr.iType == InstructionType.LUI) {
					temp = Integer.parseInt(instr.bitString.substring(16,32), 2);
					instr.arg2Val = temp;	//Immediate
					instr.arg2Ready = true;
				} else {	// ORI/ADDI
					temp = Integer.parseInt(instr.bitString.substring(6,11), 2);
					instr.arg1Reg = temp;	//Operand1
					temp = Integer.parseInt(instr.bitString.substring(16,32), 2);
					instr.arg2Val = toSigned(temp, 16);
					instr.arg2Ready = true;
				}
				break;
			case SHIFT: 			//Shift instructions
				temp = Integer.parseInt(instr.bitString.substring(16,21), 2);
				instr.dest1Reg = temp;
				temp = Integer.parseInt(instr.bitString.substring(6,11), 2);
				instr.arg1Reg = temp;		//Value to be shifted
				temp = Integer.parseInt(instr.bitString.substring(21,26), 2);
				instr.arg2Val = temp;	//Shift amount
				instr.arg2Ready = true;
				break;
			case SHIFTV:
				temp = Integer.parseInt(instr.bitString.substring(16,21), 2);
				instr.dest1Reg = temp;
				temp = Integer.parseInt(instr.bitString.substring(6,11), 2);
				instr.arg1Reg = temp;		//Value to be shifted
				temp = Integer.parseInt(instr.bitString.substring(11,16), 2);
				instr.arg2Reg = temp;	//Shift amount reg
				break;
			case MEM:				//Load/Store instructions
				if (instr.iType == InstructionType.LOAD) {
					temp = Integer.parseInt(instr.bitString.substring(11,16), 2);
					instr.dest1Reg = temp;	//Destination
					temp = Integer.parseInt(instr.bitString.substring(6,11), 2);
					instr.arg1Reg = temp;		//Base address
					temp = Integer.parseInt(instr.bitString.substring(16,32), 2);
					instr.arg2Val = toSigned(temp, 16);	//Immediate offset
					instr.arg2Ready = true;
				} else {	//Store
					temp = Integer.parseInt(instr.bitString.substring(11,16), 2);
					instr.arg1Reg = temp;	//Register to be stored
					temp = Integer.parseInt(instr.bitString.substring(6,11), 2);
					instr.arg2Reg = temp;	//Base address
					temp = Integer.parseInt(instr.bitString.substring(16,32), 2);
					instr.arg3Val = toSigned(temp, 16);	//Immediate offset
				}
				break;
			case BRANCH: 			//Branch/jump instructions
				if (instr.iType == InstructionType.JALR) {
					temp = Integer.parseInt(instr.bitString.substring(16,21), 2);
					instr.dest1Reg = temp;	//Destination for PC+1
					temp = Integer.parseInt(instr.bitString.substring(6,11), 2);
					instr.arg1Reg = temp;	//Absolute branch target
					
				} else {	// BEZ or BLZ...
					temp = Integer.parseInt(instr.bitString.substring(6,11), 2);
					instr.arg1Reg = temp;	//Value to be compared
					temp = Integer.parseInt(instr.bitString.substring(16,32), 2);
					instr.arg2Val = toSigned(temp, 16);	//Relative branch target
					instr.arg2Ready = true;
				}
				break;
		}
	}
	

	
	private int toSigned(int x, int length) {
		int maxPos = (int)Math.pow(2, length-1) - 1;
		if (x > maxPos) {
			return x - (2*(maxPos + 1));
		} else {
			return x;
		}
	}
}
